package models;

/**
 * Attractors by NiTh & M4rt
 */

import java.awt.Color;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;

import odk.lang.FastMath;

import org.cheffo.jeplite.JEP;
import org.cheffo.jeplite.ParseException;

import processing.core.PApplet;
import views.Canvas;
import controllers.MainController;


public class AttractorGen extends PApplet
{
	/**
	 * Generated serial ID
	 */
	private static final long	serialVersionUID	= 5420148812758840100L;

	/**
	 * ################### # Constants # ###################
	 */

	public static int			MAX_NR_OF_PTS		= 12000000;
	public static int			NR_OF_PTS			= 2000000;

	/**
	 * ################### # Rest of vars # ###################
	 */
	
	private MainController		mainController		= null;
	private boolean				OUTPUT_MODE			= false;
	
	private int					outputQuantity		= 0;
	
	public Vector3[]			pointList			= null;	

	public static float			PRANDTL				= 10;
	public static float			RAYLEIGH			= 28.0f;
	public static float			BETA				= (8.0f / 3.0f);
	public static float			A					= 2.242f;
	public static float			B					= 0.432f;
	public static float			C					= 0.65199995f;
	public static float			D					= -2.428f;
	public static float			E					= 1.0f;
	
	public static float			STEP				= 0.005f;
	
	private JEP					eqParser			= null;
	private PointFromTable		pftX				= null;
	private PointFromTable		pftY				= null;
	private PointFromTable		pftZ				= null;
	
//	private ScriptEngineManager mgr					= null;
//	private ScriptEngine 		jsEngine			= null;
	
	/**
	 * ################## # End of vars # ##################
	 */
	
	/**
	 * 
	 */
	public AttractorGen(MainController mainController, boolean OUTPUT_MODE, int outputQuantity)
	{
		this.mainController = mainController;
		
		// boolean for output mode used to output data to a file
		this.OUTPUT_MODE = OUTPUT_MODE;
		
		// the quantity of data sets to output
		this.outputQuantity = outputQuantity;
		
		// create and prepare the default list for points
		createtLists();
		
		// set the parsing engine
		setEqParser(new JEP());
		
		// initialize instances of PFT to set them with corresponding functions
		pftX = new PointFromTable(this, "X");
		pftY = new PointFromTable(this, "Y");
		pftZ = new PointFromTable(this, "Z");
		
		// initialize the parser by adding the default set of functions
		// and constants to the parser
		getEqParser().addStandardFunctions();
		getEqParser().addStandardConstants();
		
		// add custom functions for retrieving points from the list of points
		getEqParser().addFunction("getX", pftX);
		getEqParser().addFunction("getY", pftY);
		getEqParser().addFunction("getZ", pftZ);
		
		// add custom fast sine and cosine functions to the parser
		getEqParser().addFunction("sinf", new FastSin());
		getEqParser().addFunction("cosf", new FastCos());
		
		// add and set custom variables to the parser (for presentation purposes)
		getEqParser().addVariable("A", A);
		getEqParser().addVariable("B", B);
		getEqParser().addVariable("C", C);
		getEqParser().addVariable("D", D);
		getEqParser().addVariable("E", E);
		
		// add and set the iteration variables
		getEqParser().addVariable("currentPos", 0.d);
		getEqParser().addVariable("offset", 1.d);
		
		/**
		 * The lower part used to be a JS parser but it just overrides
		 * the functionality of the JEP parser providing more flexible
		 * language unfortunately having lower performance
		 */
		{
//			mgr = new ScriptEngineManager();
//			setJsEngine(mgr.getEngineByName("JavaScript"));
//			
//			getJsEngine().put("FastMath", new FastMathWrapper());
//			getJsEngine().put("A", A);
//			getJsEngine().put("B", B);
//			getJsEngine().put("C", C);
//			getJsEngine().put("D", D);
//			getJsEngine().put("E", E);
//			getJsEngine().put("currentPos", 0.d);
//			getJsEngine().put("offset", 1.d);
//			getJsEngine().put("ag", this);
		}
	}

	public void setEqParser(JEP eqParser)
	{
		this.eqParser = eqParser;
	}

	public JEP getEqParser()
	{
		return eqParser;
	}

//	public void setJsEngine(ScriptEngine jsEngine)
//	{
//		this.jsEngine = jsEngine;
//	}
//
//	public ScriptEngine getJsEngine()
//	{
//		return jsEngine;
//	}
	
	/**
	 * Adds a custom variable to the JEP parser
	 * @param name - sets the name of the variable
	 * @param value - sets the value of the added variable
	 */
	public void addVar(String name, double value)
	{
		// This is a workaround and a fail-safe for removing variables
		if (eqParser.getVarNode(name) != null)
		{
			eqParser.getVarNode(name).setValue(value);
		}
		else
			eqParser.addVariable(name, value);
	}
	
	/**
	 * Removes the custom variable added to the JEP parser
	 * @param varName - name of the var to be removed
	 */
	public void removeVar(String varName)
	{
		if (eqParser.getVarNode(varName) != null)
		{
			// FIXME: The variable should be removed from here as well
			// but isn't at the time because this functionality in the JEP
			// does not exist...
			
			// TODO: Add the mentioned above functionality
		}
	}

	/**
	 * Creates and initializes the default list of points
	 */
	private void createtLists()
	{
		pointList = new Vector3[MAX_NR_OF_PTS];

		for (int i = 0; i < pointList.length; i++)
			pointList[i] = new Vector3();
		
		pointList[0].x = 10.123;
		pointList[0].y = 30.123;
		pointList[0].z = 0.123;
	}
	
	/**
	 * Get the point from desired position from the "X" point list
	 * 
	 * @param currentPos - position of the 
	 * @param offset - offset from the currentPos
	 * @return The desired point from list, if the point does not exist return 0.d
	 */
	public double getX(int currentPos, int offset)
    {
            if (pointList != null && NR_OF_PTS >= currentPos && currentPos >= 0 && currentPos - offset >= 0)
                    return pointList[currentPos - offset].x;
                    
            return 0.d;
    }
    
    public double getY(int currentPos, int offset)
    {
            if (pointList != null && NR_OF_PTS >= currentPos && currentPos >= 0 && currentPos - offset >= 0)
                    return pointList[currentPos - offset].y;
                    
            return 0.d;
    }
    
    public double getZ(int currentPos, int offset)
    {
            if (pointList != null && NR_OF_PTS >= currentPos && currentPos >= 0 && currentPos - offset >= 0)
                    return pointList[currentPos - offset].z;
                    
            return 0.d;
    }
    
    public void updateVars(double step)
    {
    	// update the values in textfields by adding to them the STEP parameter
    	mainController.setVars(step);
    	
    	// get the updated values
    	ArrayList<String> vars = mainController.getVars();
 	
    	// update the variables with the values from the UI
    	for (int i = 0; i < vars.size(); i++)
    	{
    		if (eqParser.getVarNode("VAR" + i) != null)
    		{
    			eqParser.getVarNode("VAR" + i).setValue(Double.parseDouble(vars.get(i)));
    		}
    	}
    }
    
    public void outputData()
    {
    	BufferedWriter out = null;
    	
    	try
    	{
			out = new BufferedWriter(new FileWriter("output.csv", true));
			
			for (int i = 0; outputQuantity >= i; outputQuantity--)
    		{
    			try
				{
    				for (int j = 0; j < NR_OF_PTS; j++)
        			{
    					out.write(j + ";");
        				out.write(String.format("%,f", pointList[j].x) + ";");
    					out.write(String.format("%,f", pointList[j].y) + ";");
    					out.write(String.format("%,f", pointList[j].z) + ";");
    					
    					out.write("\r\n");
        			}
    				
    				out.write("\n\n");
				}
				catch (IOException e)
				{
					System.err.println(e.getLocalizedMessage());
				}
    		}
			
			out.close();
		}
    	catch (IOException e)
    	{
			System.err.println(e.getLocalizedMessage());
		}
    }
    
    /**
     * Calculate the attractor using the formulas entered in the textfields 
     */
	public void calc()
	{
		float distance = 0.0f;
		int color = 0;
		
		for (int i = 1; i < NR_OF_PTS; i++)
		{
			getEqParser().getVarNode("currentPos").setValue(i);

			try
			{
				getEqParser().parseExpression(mainController.getEquation("X"));
				pointList[i].x = getEqParser().getValue();
				
				getEqParser().parseExpression(mainController.getEquation("Y"));
				pointList[i].y = getEqParser().getValue();
				
				getEqParser().parseExpression(mainController.getEquation("Z"));
				pointList[i].z = getEqParser().getValue();
			}
			catch (ParseException e)
			{
				System.err.println(e.getLocalizedMessage());
			}
		
			if (Canvas.COLOR) 
            { 
                  distance = (float) (MathHelper.calcDistance(pointList[i - 1].x, pointList[i].x, 
                              pointList[i - 1].y, pointList[i].y, 
                              pointList[i - 1].z, pointList[i].z)); 
                   
                  color = Color.HSBtoRGB(distance - 0.81f, distance * 0.49f, distance * 0.12f);       
            } 
            else if (Canvas.BW) 
                  color = Color.HSBtoRGB(0.1f, 0.2f, 0.03f); 
             
            Canvas.doubleBuffer.put(pointList[i].x * 100); 
            Canvas.doubleBuffer.put(pointList[i].y * 100); 
            Canvas.doubleBuffer.put(pointList[i].z * 100); 
             
            Canvas.byteBuffer.put((byte) ((color >> 16) & 0xFF)); 
            Canvas.byteBuffer.put((byte) ((color >> 8) & 0xFF)); 
            Canvas.byteBuffer.put((byte) (color & 0xFF));
		}
		
		if (OUTPUT_MODE)
			outputData();
	}
	
	/**
	 * This used to be a JS parser calculating function.
	 * @deprecated DO NOT USE THIS UNLESS YOU KNOW WHAT TO DO TO MAKE IT WORK!
	 */
	public void calcJS()
	{
//		float distance = 0.0f;
//		int color = 0;
//		
//		for (int i = 1; i < NR_OF_TEST_PTS; i++)
//		{
//			getJsEngine().put("currentPos", i);
//			
//			try
//			{
//				getJsEngine().eval("ag.pointList[currentPos].x =(FastMath.sin(A * ag.pointList[currentPos - 1].y) - ag.pointList[currentPos - 1].z * FastMath.cos(B * ag.pointList[currentPos - 1].x))");
//				getJsEngine().eval("ag.pointList[currentPos].y =(ag.pointList[currentPos - 1].z * FastMath.sin(C * ag.pointList[currentPos - 1].x) + FastMath.cos(D * ag.pointList[currentPos - 1].y))");
//				getJsEngine().eval("ag.pointList[currentPos].z = (FastMath.sin(ag.pointList[currentPos - 1].x))");
//			}
//			catch (ScriptException e)
//			{
//				System.err.println(e.getLocalizedMessage());
//			}
//
//			if (Canvas.COLOR) 
//            { 
//                  distance = (float) (MathHelper.calcDistance(pointList[i - 1].x, pointList[i].x, 
//                              pointList[i - 1].y, pointList[i].y, 
//                              pointList[i - 1].z, pointList[i].z)); 
//                   
//                  color = Color.HSBtoRGB(distance - 0.81f, distance * 0.49f, distance * 0.12f);       
//            } 
//            else if (Canvas.BW) 
//                  color = Color.HSBtoRGB(0.1f, 0.2f, 0.03f); 
//			
//			Canvas.doubleBuffer.put(pointList[i].x * 100);
//			Canvas.doubleBuffer.put(pointList[i].y * 100);
//			Canvas.doubleBuffer.put(pointList[i].z * 100);
//			
//			Canvas.byteBuffer.put((byte) ((color >> 16) & 0xFF));
//			Canvas.byteBuffer.put((byte) ((color >> 8) & 0xFF));
//			Canvas.byteBuffer.put((byte) (color & 0xFF));
//		}
	}

	/**
	 * Calculate the predefined DoJong attractor
	 */
	public void calcDeJong()
	{
		float distance = 0.0f;
		int color = 0;
		
		for (int i = 1; i < 1000; i++)
		{
			pointList[i].x = (FastMath.sin(A * pointList[i - 1].x) + FastMath.cos(B * pointList[i - 1].y));
			pointList[i].y = (FastMath.sin(C * pointList[i - 1].y) + FastMath.cos(D * pointList[i - 1].x));
			pointList[i].z = (FastMath.sin(pointList[i - 1].x));
		}

		for (int i = 1001; i < NR_OF_PTS; i++)
		{
			pointList[i].x = (FastMath.sin(A * pointList[i - 1].x) + FastMath.cos(B * pointList[i - 1].y));
			pointList[i].y = (FastMath.sin(C * pointList[i - 1].y) + FastMath.cos(D * pointList[i - 1].x));
			pointList[i].z = (FastMath.sin(pointList[i - 1].x));

			if (Canvas.COLOR) 
            { 
                  distance = (float) (MathHelper.calcDistance(pointList[i - 1].x, pointList[i].x, 
                              pointList[i - 1].y, pointList[i].y, 
                              pointList[i - 1].z, pointList[i].z)); 
                   
                  color = Color.HSBtoRGB(distance - 0.81f, distance * 0.49f, distance * 0.12f);       
            } 
            else if (Canvas.BW) 
                  color = Color.HSBtoRGB(0.1f, 0.2f, 0.03f); 

			Canvas.doubleBuffer.put(pointList[i].x * 100);
			Canvas.doubleBuffer.put(pointList[i].y * 100);
			Canvas.doubleBuffer.put(pointList[i].z * 100);

			Canvas.byteBuffer.put((byte) ((color >> 16) & 0xFF));
			Canvas.byteBuffer.put((byte) ((color >> 8) & 0xFF));
			Canvas.byteBuffer.put((byte) (color & 0xFF));
		}
		
		if (OUTPUT_MODE)
			outputData();
	}
	
	/**
	 * Calculate the predefined Pickover attractor
	 */
	public void calcPickover()
	{
		float distance = 0.0f;
		int color = 0;

		for (int i = 1; i < 1000; i++)
		{
			pointList[i].x = (FastMath.sin(A * pointList[i - 1].y) - pointList[i - 1].z * FastMath.cos(B * pointList[i - 1].x));
			pointList[i].y = (pointList[i - 1].z * FastMath.sin(C * pointList[i - 1].x) + FastMath.cos(D * pointList[i - 1].y));
			pointList[i].z = (FastMath.sin(pointList[i - 1].x));
		}
		
		for (int i = 1001; i < NR_OF_PTS; i++)
		{
			pointList[i].x = (FastMath.sin(A * pointList[i - 1].y) - pointList[i - 1].z * FastMath.cos(B * pointList[i - 1].x));
			pointList[i].y = (pointList[i - 1].z * FastMath.sin(C * pointList[i - 1].x) + FastMath.cos(D * pointList[i - 1].y));
			pointList[i].z = (FastMath.sin(pointList[i - 1].x));

			if (Canvas.COLOR) 
            { 
                  distance = (float) (MathHelper.calcDistance(pointList[i - 1].x, pointList[i].x, 
                              pointList[i - 1].y, pointList[i].y, 
                              pointList[i - 1].z, pointList[i].z)); 
                   
                  color = Color.HSBtoRGB(distance - 0.81f, distance * 0.49f, distance * 0.12f);       
            } 
            else if (Canvas.BW) 
                  color = Color.HSBtoRGB(0.1f, 0.2f, 0.03f); 
			
			Canvas.doubleBuffer.put(pointList[i].x * 100);
			Canvas.doubleBuffer.put(pointList[i].y * 100);
			Canvas.doubleBuffer.put(pointList[i].z * 100);
			
			Canvas.byteBuffer.put((byte) ((color >> 16) & 0xFF));
			Canvas.byteBuffer.put((byte) ((color >> 8) & 0xFF));
			Canvas.byteBuffer.put((byte) (color & 0xFF));
		}
		
		if (OUTPUT_MODE)
			outputData();
	}	
}
